package risoe.syslab.fhnew.hw.enocean;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


import jssc.SerialPort;
import jssc.SerialPortEvent;
import jssc.SerialPortEventListener;
import jssc.SerialPortException;

public class EnoceanReceiver implements SerialPortEventListener {

	private static final int STATE_EMPTY=1;
	private static final int STATE_SYNC1=2;
	private static final int STATE_SYNC2=3;
	private static final int STATE_HDR1=4;
	private static final int STATE_DATA=5;

	private static final int MSGLENGTH=11; // message body always 11 bytes.
	private static final char MSGTYPE_RRT=0x0B;
	private static final char MSGTYPE_RMT=0x8B;

	private int state;
	private char[] msgbuf;
	private EnoceanParser parser;
	private boolean dataReady;
	private SerialPort serialport;
	private boolean running;
	
	private Runnable runnable = new Runnable() {

		@Override
		public void run() {
			Thread tr = Thread.currentThread();
		    tr.setName( "Flexhouse Enocean Receiver" );

		    char b=0;
		    short buflen=0;
		    running=(serialport!=null)?true:false;
		    state=STATE_EMPTY;
		    while (true) {
		      synchronized (EnoceanReceiver.this) {
		    	if (!running) return;
			  }
		    	
		      b=readByte();
		      if (b==0xffff) { //this is a hack to prevent 100% CPU 
		        try {          //before the first message can be read.
		          Thread.sleep(100);
		        }
		        catch (InterruptedException e) { e.printStackTrace(); }
		      }
		      switch (state) {
		        case STATE_EMPTY:
		          if (b==EnoceanUtils.SYNCBYTE1) {
		            state=STATE_SYNC1;
		          }
		        break;
		        case STATE_SYNC1:
		          if (b==EnoceanUtils.SYNCBYTE2) {
		            state=STATE_SYNC2;
		          }
		          else if (b!=EnoceanUtils.SYNCBYTE1) {
		            state=STATE_EMPTY;
		          }
		        break;
		        case STATE_SYNC2:
		          buflen=0; //empty message buffer
		          if ((b==MSGTYPE_RRT) ||
		              (b==MSGTYPE_RMT)) {
		            msgbuf[buflen++]=b;
		            state=STATE_HDR1;
		          }
		          else if (b==EnoceanUtils.SYNCBYTE1) {
		            state=STATE_SYNC1;
		          }
		          else {
		            state=STATE_EMPTY;
		          }
		        break;
		        case STATE_HDR1:
		          msgbuf[buflen++]=b;
		          if (buflen==MSGLENGTH) {
		            state=STATE_DATA;
		          }
		        break;
		        case STATE_DATA:
		          if (b==EnoceanUtils.getChecksum(msgbuf)) {
		            parser.parseRawMessage(msgbuf.clone());
		            state=STATE_EMPTY;
		          }
		          else {
		            state=STATE_EMPTY; //discard msg with failed checksum
		          }
		        break;
		      }
		    }

		}
	};

	public EnoceanReceiver(EnoceanParser pa, SerialPort port) {
		msgbuf=new char[MSGLENGTH];
		parser=pa;
		serialport = port;
		state = STATE_EMPTY;
		
		new Thread(runnable).start();
	}
	
	public void stop(){
		synchronized (this) {
			running = false;
		}
	}

	/*
	public void handleData(){
		char b = 0;
		try {
			b = (char)(inputStream.read() & 0xFF);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// Now parse the data
		switch (state) {
		case STATE_EMPTY:
			if (b==EnoceanUtils.SYNCBYTE1) {
				state=STATE_SYNC1;
			}
			break;
		case STATE_SYNC1:
			if (b==EnoceanUtils.SYNCBYTE2) {
				state=STATE_SYNC2;
			}
			else if (b!=EnoceanUtils.SYNCBYTE1) {
				state=STATE_EMPTY;
			}
			break;
		case STATE_SYNC2:
			buflen=0; //empty message buffer
			if ((b==MSGTYPE_RRT) ||
					(b==MSGTYPE_RMT)) {
				msgbuf[buflen++]=b;
				state=STATE_HDR1;
			}
			else if (b==EnoceanUtils.SYNCBYTE1) {
				state=STATE_SYNC1;
			}
			else {
				state=STATE_EMPTY;
			}
			break;
		case STATE_HDR1:
			msgbuf[buflen++]=b;
			if (buflen==MSGLENGTH) {
				state=STATE_DATA;
			}
			break;
		case STATE_DATA:
			if (b==EnoceanUtils.getChecksum(msgbuf)) {
				parser.parseRawMessage(msgbuf.clone());
				state=STATE_EMPTY;
			}
			else {
				state=STATE_EMPTY; //discard msg with failed checksum
			}
			break;
		}
	}
	*/
	
	public char readByte() {
	    try {
	      if (dataReady) {
	    	int c = serialport.readIntArray(1)[0];
	        if (c != -1) {
	          return (char)(c&0xff);
	        }
	        else {
	          dataReady=false;
	          return 0xffff;
	        }
	      }
	    }
	    catch (SerialPortException e) {
	      e.printStackTrace();
	    }
	   return 0xffff;
	  }

	public void serialEvent(SerialPortEvent event) {
		if (event.isRXCHAR()){
			// Data is ready
			dataReady = true;
		}
	}

}
